#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>


#define LED_CNT 1
#define NAME "gpioled"

#define CMD_LED_CLOSE _IO(0xef, 0x1)
#define CMD_LED_OPEN _IO(0xef, 0x2)
#define CMD_SET_PERIOD _IO(0xef, 0x3)

struct gpioled_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int led_gpio;
    int period;
    struct timer_list timer;
    spinlock_t lock;
};

static struct gpioled_dev gpioled;


static int gpioled_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int gpioled_release(struct inode *inode, struct file *filp)
{
    return 0;    
}

static ssize_t gpioled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static long gpioled_unlocked_ioctl(struct file *filp, unsigned int cmd,
			unsigned long arg)
{
    unsigned long flags;

    spin_lock_irqsave(&gpioled.lock, flags);
    switch(cmd){
        case CMD_LED_CLOSE:
            del_timer_sync(&gpioled.timer);
            gpio_set_value(gpioled.led_gpio,0);
            break;
        case CMD_LED_OPEN:
            del_timer_sync(&gpioled.timer);
            gpio_set_value(gpioled.led_gpio,1);
            break;
        case CMD_SET_PERIOD:
            gpioled.period=arg;
            mod_timer(&gpioled.timer, jiffies+msecs_to_jiffies(arg));
            break;
        default:break;
    }
    spin_unlock_irqrestore(&gpioled.lock, flags);

    return 0;
}

static struct file_operations gpioled_fops={
    .owner=THIS_MODULE,
    .open=gpioled_open,
    .read=gpioled_read,
    .write=gpioled_write,
    .release=gpioled_release,
    .unlocked_ioctl=gpioled_unlocked_ioctl,
};

static void led_timer_callback(unsigned long arg)
{
    static bool on=1;
    unsigned long flags;

    spin_lock_irqsave(&gpioled.lock, flags);

    on=!on;
    gpio_set_value(gpioled.led_gpio,on);
    mod_timer(&gpioled.timer, jiffies+msecs_to_jiffies(gpioled.period));

    spin_unlock_irqrestore(&gpioled.lock, flags);


}

static int __init gpioled_init(void)
{
    int ret;
    const char *str;

    spin_lock_init(&gpioled.lock);

    /*0*/
    gpioled.nd=of_find_node_by_path("/led");
    ret=of_property_read_string(gpioled.nd, "status", &str);
    if(strcmp(str, "okay")){
        return -EINVAL;
    }

    ret=of_property_read_string(gpioled.nd, "compatible", &str);
    if(strcmp(str, "alientek,led")){
        return -EINVAL;
    }

    printk(KERN_INFO "led device matching successful!\n");

    /*1*/
    gpioled.led_gpio=of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    printk(KERN_INFO "gpioled: led-gpio num=%d\n", gpioled.led_gpio);

    ret=gpio_request(gpioled.led_gpio, "LED-GPIO");
    gpio_direction_output(gpioled.led_gpio, 0);

    ret=of_property_read_string(gpioled.nd, "default-state", &str);
    if(!strcmp(str, "on")){
        gpio_set_value(gpioled.led_gpio, 1);
    }
    else {
        gpio_set_value(gpioled.led_gpio, 0);
    }

    /*7*/
    if(gpioled.major){
        gpioled.devid=MKDEV(gpioled.major,0);
        ret=register_chrdev_region(gpioled.devid, LED_CNT, NAME);
    }
    else {
        ret=alloc_chrdev_region(&gpioled.devid, 0, LED_CNT, NAME);
        gpioled.major=MAJOR(gpioled.devid);
        gpioled.minor=MINOR(gpioled.devid);
    }

    printk("gpioled major=%d, minor=%d\n", gpioled.major, gpioled.minor);

    /*8*/
    gpioled.cdev.owner=THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    ret=cdev_add(&gpioled.cdev, gpioled.devid, LED_CNT);

    /*9*/
    gpioled.class=class_create(THIS_MODULE, NAME);
    gpioled.device=device_create(gpioled.class,
                                    NULL,
                                    gpioled.devid,
                                    NULL,
                                    NAME);
    
    init_timer(&gpioled.timer);
    gpioled.timer.function=led_timer_callback;

    return 0;
}

static void __exit gpioled_exit(void)
{
    del_timer_sync(&gpioled.timer);
    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, LED_CNT);
    gpio_free(gpioled.led_gpio);

    printk("gpioled_exit()\n");
}

module_init(gpioled_init);
module_exit(gpioled_exit);

MODULE_AUTHOR("xujianchao");
MODULE_DESCRIPTION("xujianchao ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");
